// Performance optimization utilities

/**
 * Debounce function to limit the rate at which a function can fire
 */
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null
  
  return function (...args: Parameters<T>) {
    if (timeout) clearTimeout(timeout)
    
    timeout = setTimeout(() => {
      func(...args)
      timeout = null
    }, wait)
  }
}

/**
 * Throttle function to ensure a function is called at most once in a specified period
 */
export function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle = false
  
  return function (...args: Parameters<T>) {
    if (!inThrottle) {
      func(...args)
      inThrottle = true
      setTimeout(() => {
        inThrottle = false
      }, limit)
    }
  }
}

/**
 * Lazy load images using Intersection Observer
 */
export function lazyLoadImages() {
  const images = document.querySelectorAll('img[data-src]')
  
  const imageObserver = new IntersectionObserver((entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const img = entry.target as HTMLImageElement
        const src = img.getAttribute('data-src')
        
        if (src) {
          img.src = src
          img.removeAttribute('data-src')
          observer.unobserve(img)
        }
      }
    })
  })
  
  images.forEach((img) => imageObserver.observe(img))
  
  return () => {
    images.forEach((img) => imageObserver.unobserve(img))
  }
}

/**
 * Request idle callback with fallback
 */
export function requestIdleCallback(
  callback: () => void,
  options?: { timeout?: number }
): number {
  if ('requestIdleCallback' in window) {
    return window.requestIdleCallback(callback, options)
  }
  
  // Fallback to setTimeout
  return window.setTimeout(callback, options?.timeout || 1)
}

/**
 * Cancel idle callback with fallback
 */
export function cancelIdleCallback(id: number): void {
  if ('cancelIdleCallback' in window) {
    window.cancelIdleCallback(id)
  } else {
    window.clearTimeout(id)
  }
}

/**
 * Memoize function results
 */
export function memoize<T extends (...args: any[]) => any>(
  fn: T,
  getKey?: (...args: Parameters<T>) => string
): T {
  const cache = new Map()
  
  return ((...args: Parameters<T>) => {
    const key = getKey ? getKey(...args) : JSON.stringify(args)
    
    if (cache.has(key)) {
      return cache.get(key)
    }
    
    const result = fn(...args)
    cache.set(key, result)
    
    // Limit cache size
    if (cache.size > 100) {
      const firstKey = cache.keys().next().value
      cache.delete(firstKey)
    }
    
    return result
  }) as T
}

/**
 * Virtual scrolling helper
 */
export class VirtualScroller {
  private container: HTMLElement
  private itemHeight: number
  private items: any[]
  private visibleRange: { start: number; end: number }
  
  constructor(container: HTMLElement, itemHeight: number, items: any[]) {
    this.container = container
    this.itemHeight = itemHeight
    this.items = items
    this.visibleRange = { start: 0, end: 0 }
    
    this.calculateVisibleRange()
    this.container.addEventListener('scroll', this.onScroll)
  }
  
  private onScroll = throttle(() => {
    this.calculateVisibleRange()
  }, 100)
  
  private calculateVisibleRange() {
    const scrollTop = this.container.scrollTop
    const containerHeight = this.container.clientHeight
    
    this.visibleRange.start = Math.floor(scrollTop / this.itemHeight)
    this.visibleRange.end = Math.ceil((scrollTop + containerHeight) / this.itemHeight)
  }
  
  getVisibleItems() {
    return this.items.slice(this.visibleRange.start, this.visibleRange.end)
  }
  
  destroy() {
    this.container.removeEventListener('scroll', this.onScroll)
  }
}

/**
 * Performance monitoring
 */
export class PerformanceMonitor {
  private marks: Map<string, number> = new Map()
  
  mark(name: string) {
    this.marks.set(name, performance.now())
  }
  
  measure(name: string, startMark: string, endMark?: string) {
    const start = this.marks.get(startMark)
    const end = endMark ? this.marks.get(endMark) : performance.now()
    
    if (start && end) {
      const duration = end - start
      console.log(`Performance [${name}]: ${duration.toFixed(2)}ms`)
      
      // Send to analytics if needed
      if (window.gtag) {
        window.gtag('event', 'timing_complete', {
          name,
          value: Math.round(duration),
        })
      }
      
      return duration
    }
    
    return 0
  }
  
  clearMarks() {
    this.marks.clear()
  }
}

/**
 * Preload critical resources
 */
export function preloadResources(urls: string[]) {
  urls.forEach((url) => {
    const link = document.createElement('link')
    link.rel = 'preload'
    
    // Detect resource type
    if (url.endsWith('.js')) {
      link.as = 'script'
    } else if (url.endsWith('.css')) {
      link.as = 'style'
    } else if (/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(url)) {
      link.as = 'image'
    } else if (/\.(woff|woff2|ttf|otf)$/i.test(url)) {
      link.as = 'font'
      link.crossOrigin = 'anonymous'
    }
    
    link.href = url
    document.head.appendChild(link)
  })
}

/**
 * Web Worker utility for heavy computations
 */
export class WorkerPool {
  private workers: Worker[] = []
  private queue: Array<{ data: any; resolve: (value: any) => void }> = []
  private busyWorkers: Set<Worker> = new Set()
  
  constructor(workerScript: string, poolSize: number = 4) {
    for (let i = 0; i < poolSize; i++) {
      const worker = new Worker(workerScript)
      this.workers.push(worker)
    }
  }
  
  async execute(data: any): Promise<any> {
    return new Promise((resolve) => {
      const availableWorker = this.workers.find((w) => !this.busyWorkers.has(w))
      
      if (availableWorker) {
        this.runWorker(availableWorker, data, resolve)
      } else {
        this.queue.push({ data, resolve })
      }
    })
  }
  
  private runWorker(worker: Worker, data: any, resolve: (value: any) => void) {
    this.busyWorkers.add(worker)
    
    const handleMessage = (e: MessageEvent) => {
      resolve(e.data)
      worker.removeEventListener('message', handleMessage)
      this.busyWorkers.delete(worker)
      
      // Process queue
      if (this.queue.length > 0) {
        const next = this.queue.shift()
        if (next) {
          this.runWorker(worker, next.data, next.resolve)
        }
      }
    }
    
    worker.addEventListener('message', handleMessage)
    worker.postMessage(data)
  }
  
  terminate() {
    this.workers.forEach((worker) => worker.terminate())
    this.workers = []
    this.queue = []
    this.busyWorkers.clear()
  }
}

// Extend window type for gtag
declare global {
  interface Window {
    gtag?: (...args: any[]) => void
  }
}